#include	"CenterService.h"
#include	"ZoneConnector.h"
#include	"Database.h"
#include	"../core/Cache.h"
#include	"../core/Balancer.h"
#include	"../core/OnlinePlayer.h"

#include	<protocols/Center.pb.h>
#include	<protocols/Zone.pb.h>
#include	<protocols/DBProxy.pb.h>
#include	<redis++.h>
#include	<Script.h>
#include	<Json.h>

namespace {

	void Register(uint64_t connect, Proto::CmdRegister * cmd) {
		if (cmd) Balancer::Instance().Add(connect, cmd->type(), cmd->addr().ip(), cmd->addr().port());
	}

	void KickoutSafe(Proto::CmdKickout * cmd) {
		if (cmd) GOnlinePlayer.Logout(cmd->pid());
	}

	void RunScript(uint64_t from, Proto::CmdScript * cmd) {
		if (cmd) GLua.Call("Dispatch", "Process", from, cmd->table(), cmd->func(), cmd->param());
	}

	void Logout(Proto::Center::CmdLogout * cmd) {
		if (cmd) GOnlinePlayer.Logout(cmd->pid());
	}

	bool GetRoleList(const std::string & account, uint64_t at, uint32_t sid, uint32_t serial) {
		Proto::DBProxy::ReqRoleList req;
		req.set_account(account);
		req.set_at(at);
		req.set_sid(sid);
		return GDatabase.Send(Proto::REQ_ROLELIST, req, serial);
	}

	void FailedVerify(Sender * sender, const std::string & account, Proto::Status err, uint32_t serial) {
		Proto::Center::RspVerify rsp;
		rsp.set_account(account);
		rsp.set_status(err);
		sender->Reply(Proto::RSP_VERIFY, rsp, serial);
	}

	void Verify(Sender * sender, Proto::Center::ReqVerify * req, uint32_t serial) {
		if (!req) return;

		const std::string & account = req->account();
		OnlinePlayer * find = GOnlinePlayer.FindByAccount(account);

		if (!find && GService.NeedCheckAccount()) {
			if (req->code().empty()) {
				FailedVerify(sender, account, Proto::S_ENeedActive, serial);
				return;
			}

			Proto::Zone::ReqActiveAccount request;
			request.set_account(account);
			request.set_code(req->code());
			request.set_at(sender->Id());
			request.set_sid(req->sid());

			if (!GZone.Send(Proto::REQ_ACTIVEACCOUNT, request, serial)) {
				FailedVerify(sender, account, Proto::S_ENetwork, serial);
			}

			return;
		}

		GOnlinePlayer.Kickout(find, Proto::CmdKickout_Reason_Login);
		if (!GetRoleList(account, sender->Id(), req->sid(), serial))
			FailedVerify(sender, account, Proto::S_ENetwork, serial);
	}

	void CreateRole(Sender * sender, Proto::Center::ReqCreate * req, uint32_t serial) {
		if (!req) return;

		const std::string & account = req->account();
		int status = GOnlinePlayer.CheckSession(account, req->session());
		if (status == Proto::S_Success) {
			OnlinePlayer * find = GOnlinePlayer.FindByAccount(account);
			GOnlinePlayer.Kickout(find, Proto::CmdKickout_Reason_Login);

			Proto::Zone::ReqCreateRole request;
			request.set_account(account);
			request.set_name(req->name());
			request.set_at(sender->Id());
			request.set_sid(req->sid());

			if (!GZone.Send(Proto::REQ_CREATEROLE, request, serial)) {
				status = Proto::S_ENetwork;
			} else {
				return;
			}
		}

		Proto::Center::RspCreate rsp;
		rsp.set_account(account);
		rsp.set_status((Proto::Status)status);
		sender->Reply(Proto::RSP_CREATEROLE, rsp, serial);
	}

	bool GetRole(const std::string & account, uint64_t pid, uint64_t at, uint32_t serial) {
		return GCache.Get<Core::RoleData>(pid, [=](Core::RoleData * role) {
			if (!role || role->account() != account) {
				Proto::DBProxy::ReqRole req;
				req.set_account(account);
				req.set_pid(pid);
				req.set_at(at);

				GDatabase.Send(Proto::REQ_GETROLE, req, serial);
			} else {
				Proto::Center::RspLogin rsp;
				rsp.set_account(account);
				rsp.set_status(Proto::S_Success);
				rsp.mutable_role()->CopyFrom(*role);

				Sender * sender = GService.Find(at);
				if (sender) sender->Reply(Proto::RSP_LOGIN, rsp, serial);
			}
		});
	}

	void Login(Sender * sender, Proto::Center::ReqLogin * req, uint32_t serial) {
		if (!req) return;

		auto & account = req->account();
		auto & session = req->session();
		uint64_t pid = req->pid();

		int status = GOnlinePlayer.CheckSession(account, session);
		if (status == Proto::S_Success) {
			OnlinePlayer * player = GOnlinePlayer.FindByAccount(account);
			if (player) {
				GOnlinePlayer.Kickout(player, Proto::CmdKickout_Reason_Login);
				status = Proto::S_ELogined;
			} else if (!GetRole(account, req->pid(), sender->Id(), serial)) {
				status = Proto::S_ENetwork;
			} else {
				return;
			}
		}

		Proto::Center::RspLogin rsp;
		rsp.set_account(account);
		rsp.set_status((Proto::Status)status);
		sender->Reply(Proto::RSP_LOGIN, rsp, serial);
	}

	void Rename(Proto::Center::ReqRename * req, uint32_t serial) {
		if (!req) return;

		auto & name = req->name();
		auto player = GOnlinePlayer.Find(req->pid());
		auto check = GOnlinePlayer.Find(name);
		if (!player) {
			LOG_WARN("[center]Rename player[%llu] failed. Not online!", req->pid());
		} else if (check) {
			Proto::Center::RspRename rsp;
			rsp.set_pid(req->pid());
			rsp.set_name(name);
			rsp.set_status(Proto::S_ENameUsed);
			player->Send(Proto::RSP_RENAME, rsp, serial);
		} else {
			Proto::Zone::ReqRename rename;
			rename.set_pid(req->pid());
			rename.set_name(name);
			GZone.Send(Proto::REQ_RENAME, rename, serial);
		}
	}
}

void CenterService::OnReceive(Sender * sender, Protocol * msg) {
	switch (msg->pf) {
	case Proto::CMD_REGISTER: Register(sender->Id(), Cast<Proto::CmdRegister>(msg)); break;
	case Proto::CMD_KICKOUTSAFE: KickoutSafe(Cast<Proto::CmdKickout>(msg)); break;
	case Proto::CMD_SCRIPT: RunScript(sender->Id(), Cast<Proto::CmdScript>(msg)); break;
	case Proto::CMD_LOGOUT: Logout(Cast<Proto::Center::CmdLogout>(msg)); break;
	case Proto::REQ_VERIFY: Verify(sender, Cast<Proto::Center::ReqVerify>(msg), msg->serial); break;
	case Proto::REQ_CREATEROLE: CreateRole(sender, Cast<Proto::Center::ReqCreate>(msg), msg->serial); break;
	case Proto::REQ_LOGIN: Login(sender, Cast<Proto::Center::ReqLogin>(msg), msg->serial); break;
	case Proto::REQ_RENAME: Rename(Cast<Proto::Center::ReqRename>(msg), msg->serial); break;
	default:
		LOG_ERR("[center]Received unspported message. PF : %d", (int)msg->pf);
		break;
	}
}

void CenterService::OnClose(Sender * sender, ENet::Close reason) {
	IBaseService::OnClose(sender, reason);
	Balancer::Instance().Del(sender->Id());
}

void CenterService::SendAllGame(int pf, const google::protobuf::Message & msg) {
	Balancer::Instance().Foreach(Proto::ST_GS, [=, &msg](Balancer::Device * device) {
		Sender * sender = Find(device->connect);
		if (sender) sender->Reply(pf, msg);
	});
}

void CenterService::AfterZone_Active(Protocol * msg) {
	auto rsp = GZone.Cast<Proto::Zone::RspActiveAccount>(msg);
	if (!rsp) return;

	const std::string & account = rsp->account();
	Proto::Status status = rsp->status();
	if (rsp->status() == Proto::S_Success) {
		if (GetRoleList(account, rsp->at(), rsp->sid(), msg->serial)) return;
		status = Proto::S_ENetwork;
	}

	Sender * sender = Find(rsp->at());
	if (!sender) {
		LOG_ERR("[center]Failed verify after zone active account (%s). Due to login server lost...", account.c_str());
		return;
	}

	Proto::Center::RspVerify reply;
	reply.set_account(account);
	reply.set_status(status);
	sender->Reply(Proto::RSP_VERIFY, reply, msg->serial);
}

void CenterService::AfterZone_Create(Protocol * msg) {
	auto rsp = GZone.Cast<Proto::Zone::RspCreateRole>(msg);
	if (!rsp) return;

	const std::string & account = rsp->account();
	Proto::Status status = rsp->status();
	uint64_t pid = rsp->pid();

	Proto::Center::RspCreate reply;
	reply.set_account(account);
	reply.set_status((Proto::Status)status);

	if (status == Proto::S_Success) {
		Core::RoleData role;
		role.set_pid(pid);
		role.set_account(account);
		role.set_sid(rsp->sid());
		role.set_name(rsp->name());
		role.set_level(1);
		role.set_create_time((uint32_t)time(NULL));
		role.set_last_login_time(0);

		std::string buffer = role.SerializeAsString();
		std::string type = role.GetDescriptor()->name();

		bool success = GCache.Flush(pid, role);
		if (success) {
			Core::RolePreviewData * data = reply.mutable_role();
			data->set_pid(pid);
			data->set_name(rsp->name());
			data->set_level(1);
		} else {
			reply.set_status(Proto::S_ENetwork);
		}		
	}

	Sender * sender = Find(rsp->at());
	if (sender) sender->Reply(Proto::RSP_CREATEROLE, reply, msg->serial);
}

void CenterService::AfterZone_Rename(Protocol * msg) {
	auto rsp = GZone.Cast<Proto::Zone::RspRename>(msg);
	if (!rsp) return;

	auto player = GOnlinePlayer.Find(rsp->pid());
	if (!player) {
		LOG_ERR("[center]Rename failed after zone response. Player[%llu] offline", rsp->pid());
		return;
	}

	Proto::Center::RspRename reply;
	reply.set_pid(rsp->pid());
	reply.set_name(rsp->name());

	if (rsp->success()) {
		GOnlinePlayer.Rename(player, rsp->name());
		reply.set_status(Proto::S_Success);
	} else {
		reply.set_status(Proto::S_ENameUsed);
	}

	player->Send(Proto::RSP_RENAME, reply, msg->serial);
}

void CenterService::AfterDB_RoleList(Protocol * msg) {
	auto rsp = Cast<Proto::DBProxy::RspRoleList>(msg);
	if (!rsp) return;

	const std::string & account = rsp->account();
	std::string session = GOnlinePlayer.CreateSession(account);

	Proto::Center::RspVerify reply;
	reply.set_account(account);
	reply.set_session(session);

	Balancer::Device * host = Balancer::Instance().Peek(Proto::ST_GS);
	if (host) {
		reply.set_status(Proto::S_Success);

		Proto::Address * addr = reply.mutable_addr();
		addr->set_ip(host->ip);
		addr->set_port(host->port);

		if (rsp->roles_size() > 0) reply.mutable_roles()->CopyFrom(rsp->roles());
	} else {
		reply.set_status(Proto::S_EServer);
	}
	
	Sender * sender = Find(rsp->at());
	if (sender) sender->Reply(Proto::RSP_VERIFY, reply, msg->serial);
}

void CenterService::AfterDB_Role(Protocol * msg) {
	auto rsp = Cast<Proto::DBProxy::RspRole>(msg);
	if (!rsp) return;

	Proto::Center::RspLogin reply;
	reply.set_account(rsp->account());
	reply.set_status(rsp->status());

	if (rsp->status() == Proto::S_Success) {
		auto role = rsp->role();
		GOnlinePlayer.Launch(rsp->at(), role);
		GCache.Save(role.pid(), role);
		reply.mutable_role()->CopyFrom(role);
	}

	Sender * sender = Find(rsp->at());
	if (sender) sender->Reply(Proto::RSP_LOGIN, reply, msg->serial);
}